import express, { NextFunction, Request, Response } from 'express';
import next from 'next';
import { IBrowser, UAParser } from 'ua-parser-js';
import { configs, consts } from '../src/config';
import {
  createAuthMiddleware,
  isAuthenticated,
  isAuthorized,
  isWebAuthorized,
  isWebRequest,
} from './middleware/auth';
import { createCorsMiddleware } from './middleware/cors';
import createProxyMiddleware from './middleware/proxy';
import { createSessionMiddleware } from './middleware/session';
import { UserLoginStatus, UserRole } from './model/user';
import { createApiRouter } from './routes/api';
import { createUserRouter } from './routes/user';

/**
 * Custom Server Implementation for frontend API endpoint.
 * API Proxy is one of main functionality of frontend server but
 * nework error such as 'ECONNREFUSED' are NOT captured by browser.
 *
 * To tackle this issue, Next.js server SHOULD be capable of handling network error but
 * unfortunately built-in server DOES NOT support it.
 *
 * For more details, please visit
 * - https://nextjs.org/docs/api-reference/next.config.js/rewrites
 * - https://nextjs.org/docs/advanced-features/custom-server
 * - https://nextjs.org/docs/api-routes/api-middlewares
 * - https://github.com/chimurai/http-proxy-middleware
 *
 * Reference:
 * - https://medium.com/bb-tutorials-and-thoughts/next-js-how-to-proxy-to-backend-server-987174737331
 */
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const emptyHandler = (req: Request, res: Response, next: NextFunction) => {
  return next();
};
const uaHandler = (req: Request, res: Response, next: NextFunction) => {
  if (!isWebRequest(req, true)) {
    return next();
  }
  // get user-agent header
  const parser = new UAParser(req.headers['user-agent']);
  const result = parser.getResult();
  console.debug(`User-Agent: ${JSON.stringify(result)}`);
  const blockedBrowsers = ['IE', 'Edge'];
  const browser: IBrowser = result.browser;
  if (blockedBrowsers.includes(browser.name as string)) {
    console.warn(
      `Not supported browser: ${browser.name} ${browser.version}, path: ${req.path}`,
    );
    return res.status(200).send(consts.uaWarnHtmlText);
  } else {
    return next();
  }
};

/**
 * express middleware is a chain of handlers,
 * so order of handler is important and make sure that your logic is working fine.
 */
const createExpressServer = (
  name: string,
  isAuth: boolean, // On/Off authentication
  isRbac: boolean, // On/Off RBAC
  handler?: any,
): express.Application => {
  const reqHandler = handler ? handler : handle;
  const server: express.Application = express();
  server.locals.name = name;
  // set trust proxy
  server.set('trust proxy', true);
  // initializing Bodyparser
  server.use(express.urlencoded({ extended: true })); // parser for form
  // initializing sesson middleware
  createSessionMiddleware(server);
  createAuthMiddleware(server);
  // initializing cors middleware
  server.use('/api', createCorsMiddleware);
  // initializing routers
  createUserRouter(server);
  createApiRouter(server);
  if (isAuth) {
    server.use(isAuthenticated);
  }

  if (process.env.APP_ENV === 'localhost' && name !== 'auth-test') {
    // [DEV] create fake user, exception handling is NOT required.
    const newUser = {
      uid: '718e8554-563b-48be-a961-5c3647b021ea',
      info: {
        email: 'guest@samsung.com',
        loginid: 'guest',
        username: 'Samsung Kim',
        ugroup: 'System R&D Group',
        role: UserRole.Admin,
      },
      status: { loginStatus: UserLoginStatus.Approved },
    };
    server.locals.fakeUser = newUser;
    console.debug(`[DEV-ONLY] Fake User: ${JSON.stringify(server.locals.fakeUser)}`);
  }

  // initializing proxy middleware for Data API
  server.use(
    configs.apiPrefix,
    isRbac ? isAuthorized : emptyHandler,
    createProxyMiddleware,
  );
  // serve static resources for authenticated user.
  server.all(
    '*',
    isRbac ? isWebAuthorized : emptyHandler,
    uaHandler,
    (req: Request, res: Response) => {
      return reqHandler(req, res);
    },
  );
  return server;
};

export const NextApp = app;

export const CustomServer = createExpressServer;
